package mirrormonkey.util.annotations.parsing;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

import mirrormonkey.util.annotations.control.AnnotationOverride;
import mirrormonkey.util.annotations.control.DefinePreset;
import mirrormonkey.util.annotations.control.IRClass;

/**
 * Contains the control flow that is used when parsing annotations of entity
 * classes.
 * 
 * @author Philipp Christian Loewner
 * 
 */
@IRClass(ClassIR.class)
public class AnnotationParser {

	/**
	 * Users (and subsystems) may define preset classes that contain classes,
	 * methods and fields which are in turn annotated with annotations from this
	 * framework. Users can map these classes, methods and fields to annotation
	 * classes using the <tt>DefinePreset</tt> annotation.<br>
	 * 
	 * Whenever a class, method or field is annotated with the specified
	 * annotation type, the annotations of the class, method or field defining
	 * the matching <tt>DefinePreset</tt> annotation will be parsed prior to
	 * parsing the class, method or field that was annotated with the
	 * user-implemented annotation class.<br>
	 * 
	 * Preset classes can be added using the <tt>parsePresetClass</tt> method of
	 * this class.<br>
	 * 
	 * The IR data structures will access this map using the
	 * <tt>getPresetElement</tt> method of this class.<br>
	 */
	protected final Map<Class<?>, AnnotatedElement> presets = new HashMap<Class<?>, AnnotatedElement>();

	/**
	 * The <tt>IRClass</tt> annotation to use for the default class IR.
	 */
	public IRClass defaultClassIR;

	/**
	 * Ugly field that member IRs use to signal that parsing every member of the
	 * current class again will be required.
	 */
	public boolean reparseFlag = false;

	/**
	 * Convenience field that is used to find control annotations that are
	 * defined by presets.
	 */
	public final AnnotationIR findIRClass;

	/**
	 * Creates a new <tt>AnnotationParser</tt> that will read the default
	 * <tt>IRClass</tt> to use for classes from its class hierarchy.
	 */
	public AnnotationParser() {
		for (Class<?> i = getClass(); defaultClassIR == null; i = i
				.getSuperclass()) {
			defaultClassIR = i.getAnnotation(IRClass.class);
		}
		findIRClass = new AnnotationIR(this, null);
		findIRClass.addCollectType(IRClass.class);
		findIRClass.addCollectType(AnnotationOverride.class);
	}

	/**
	 * Parses the hierarchy of an entity class for annotations and returns an
	 * object that contains runtime information about how entities of exactly
	 * that class must be handled by the framework. The user then has to extract
	 * unmodifiable data from the object to use it.
	 * 
	 * @param clazz
	 *            the class that should be parsed to extract data in a
	 *            <tt>ClassIR</tt>
	 * @param interfaceBound
	 *            All interfaces inheriting from this interface will be parsed.
	 *            If this is <tt>null</tt>, then every interface will be parsed.
	 * @return An <tt>ClassIR</tt> object that contains runtime information
	 *         about how to handle entities of class <tt>localClass</tt>. Every
	 *         class inheriting from <tt>ClassIR</tt> must define its own method
	 *         to extract data that will actually be used at runtime.
	 */
	public ClassIR parseClass(Class<?> clazz,
			Collection<Class<?>> interfaceBound) {
		// Hierarchies must be parsed top-to-bottom, so we use a Deque to
		// reverse
		// them
		Deque<Class<?>> implementationHierarchy = new LinkedList<Class<?>>();
		for (Class<?> c = clazz; !c.equals(Object.class); c = c.getSuperclass()) {
			implementationHierarchy.add(c);
		}

		ClassIR classIR = createDefaultClassIR(clazz);
		Deque<Class<?>> interfaceSpecifications = new LinkedList<Class<?>>();
		while (!implementationHierarchy.isEmpty()) {
			Class<?> c = implementationHierarchy.pollLast();

			// Find control annotations in the class or one of its declared
			// presets
			findIRClass.clear();
			findIRClass.parseAnnotatedElement(c);

			// Forget everything if an AnnotationOverride is defined for the
			// class or one of its presets
			if (findIRClass.getCollectedAnnotation(AnnotationOverride.class) != null) {
				classIR = createDefaultClassIR(clazz);
			}

			// If an IRClass is encountered, replace the IR used for class data
			// if requested. Always parse the IRClass annotation to determine
			// which annotation types to listen to / stop listening to / drop.
			IRClass declaredIRClass = findIRClass
					.getCollectedAnnotation(IRClass.class);
			if (declaredIRClass != null) {
				if (!declaredIRClass.value().equals(IRClass.NoChange.class)) {
					classIR = (ClassIR) reflectiveConstructor(classIR, c,
							declaredIRClass.value());
				}
				classIR.parseIRClass(declaredIRClass);
			}

			// Get every interface implemented in any way by the current class
			// and that matches the specified interface type.
			interfaceSpecifications.clear();
			recursiveInterfaceHierarchy(c.getInterfaces(),
					interfaceSpecifications, interfaceBound);

			// Rest is ugly code to parse the class (and parse it again if one
			// of the IRs requests it)
			reparseFlag = false;
			for (Class<?> i : interfaceSpecifications) {
				parseSpecificationInterface(classIR, i);
			}
			parseHierarchyClass(classIR, c);

			if (reparseFlag) {
				for (Class<?> i : interfaceSpecifications) {
					parseSpecificationInterface(classIR, i);
				}
				parseHierarchyClass(classIR, c);
			}
		}

		// Remove constructors that should not be there because they have not
		// been overridden by the last class in the hierarchy and thus do not
		// create new objects of type clazz
		classIR.cleanMemberIRS(clazz);
		return classIR;
	}

	/**
	 * Creates the default <tt>ClassIR</tt> for a class.
	 * 
	 * @param createFor
	 *            The class that the created <tt>ClassIR</tt> should represent.
	 *            This will automatically be parsed.
	 * @return A new <tt>ClassIR</tt> as specified in <tt>defaultClassIR</tt>
	 */
	public ClassIR createDefaultClassIR(Class<?> createFor) {
		ClassIR cir = (ClassIR) reflectiveConstructor(createFor,
				defaultClassIR.value());
		cir.parseIRClass(defaultClassIR);
		cir.parseAnnotatedElement(createFor);
		return cir;
	}

	/**
	 * Creates an empty <tt>AnnoationIR</tt> for an element that previously had
	 * no matching IR.
	 * 
	 * @param createFor
	 *            element that the new <tt>AnnotationIR</tt> is for
	 * @param irClass
	 *            specific class of the <tt>AnnotationIR</tt> to create
	 * @return A new <tt>AnnotationIR</tt> of type <tt>irClass</tt>
	 */
	public AnnotationIR reflectiveConstructor(AnnotatedElement createFor,
			Class<?> irClass) {
		return reflectiveConstructor(this, false, createFor, irClass);
	}

	/**
	 * Creates a new <tt>AnnotationIR</tt> for an element that previously had a
	 * matching IR and tries to extract the data from that IR.
	 * 
	 * @param parent
	 *            previously present <tt>AnnotationIR</tt>
	 * @param createFor
	 *            element that the <tt>AnnotationIR</tt> is for
	 * @param irClass
	 *            specific class of the <tt>AnnotationIR</tt> to create
	 * @return A new <tt>AnnotationIR</tt> of type <tt>irClass</tt> that will
	 *         keep all previously collected information.
	 */
	public AnnotationIR reflectiveConstructor(AnnotationIR parent,
			AnnotatedElement createFor, Class<?> irClass) {
		return reflectiveConstructor(parent, true, createFor, irClass);
	}

	/**
	 * Creates a new <tt>AnnotationIR</tt>.
	 * 
	 * @param parent
	 *            either the <tt>AnnotationParser</tt> creating the
	 *            <tt>AnnotationIR</tt> (<tt>this</tt>) or a previous
	 *            <tt>AnnotationIR</tt> for the same element.
	 * @param matchParentExactly
	 *            if <tt>true</tt>, when searching for a constructor, only exact
	 *            matches of <tt>parent's</tt> class will be accepted. If
	 *            <tt>false</tt>, every class that inherits from
	 *            <tt>parent's</tt> class will be accepted.
	 * @param createFor
	 *            the element that the <tt>AnnotationIR</tt> is created for
	 * @param irClass
	 *            the specific class of the <tt>AnnotationIR</tt> to create
	 * @return A new <tt>AnnoationIR</tt> of type <tt>irClass</tt>
	 */
	public AnnotationIR reflectiveConstructor(Object parent,
			boolean matchParentExactly, AnnotatedElement createFor,
			Class<?> irClass) {
		Constructor<?> constr;
		if ((constr = findConstructor(parent.getClass(), matchParentExactly,
				createFor.getClass(), irClass)) == null) {
			throw new IllegalArgumentException("Could not find constructor."
					+ "\nparent: " + parent + "\nmatchParentExactly: "
					+ matchParentExactly + "\ncreateFor: " + createFor
					+ "\nirClass: " + irClass);
		}
		try {
			return (AnnotationIR) constr.newInstance(parent, createFor);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Searches for a constructor defined by an IR class that matches certain
	 * criteria. Please note that using this method with an internal class will
	 * not work, as this method only finds two-argument constructors.
	 * 
	 * @param parentClass
	 *            either the <tt>AnnotationParser</tt> creating the
	 *            <tt>AnnotationIR</tt> (<tt>this</tt>) or a previous
	 *            <tt>AnnotationIR</tt> for the same element
	 * @param matchParentExactly
	 *            if <tt>true</tt>, when searching for a constructor, only exact
	 *            matches of <tt>parent's</tt> class will be accepted. If
	 *            <tt>false</tt>, every class that inherits from
	 *            <tt>parent's</tt> class will be accepted.
	 * @param elementClass
	 *            the element that the <tt>AnnotationIR</tt> is created for
	 * @param irClass
	 *            the specific class of the <tt>AnnotationIR</tt> to create.
	 *            Every constructors of this class will be checked.
	 * @return A constructor that will create a new <tt>AnnotationIR</tt> of
	 *         type <tt>irClass</tt>
	 */
	public Constructor<?> findConstructor(Class<?> parentClass,
			boolean matchParentExactly, Class<?> elementClass, Class<?> irClass) {
		Constructor<?> constr = null;
		for (Constructor<?> cc : irClass.getConstructors()) {
			Class<?>[] pTypes = cc.getParameterTypes();
			if (pTypes.length == 2 && pTypes[1].equals(elementClass)) {
				if (pTypes[0].equals(parentClass)) {
					return cc;
				} else if (!matchParentExactly
						&& pTypes[0].isAssignableFrom(parentClass)) {
					constr = cc;
				}
			}
		}
		return constr;
	}

	/**
	 * Parses the inheritance hierarchy of all the provided interface classes.
	 * Every class in the hierarchy that extends <tt>interfaceBound</tt> in any
	 * way will be added to <tt>reverseClasses</tt> depth-first, but as the name
	 * suggests, the recursion will cause the classes to be added in reverse
	 * order, causing the interfaces closer to <tt>interfaceBound</tt> in the
	 * hierarchy to be added before the ones that inherit from them.
	 * 
	 * @param c
	 *            the interface classes to parse
	 * @param reverseClasses
	 *            classes in the interface hierarchy that implement
	 *            <tt>interfaceBound</tt> will be added to this collection
	 * @param interfaceBound
	 *            some interface class that parsed interfaces must inherit from
	 *            or <tt>null</tt> if all interfaces should be parsed
	 * @return <tt>true</tt> iff one of the provided interface classes extends
	 *         <tt>interfaceBound</tt> in any way
	 */
	protected boolean recursiveInterfaceHierarchy(Class<?>[] c,
			Collection<Class<?>> reverseClasses,
			Collection<Class<?>> interfaceBound) {
		boolean b = false;
		for (Class<?> i : c) {
			if (interfaceBound == null || interfaceBound.contains(i)) {
				reverseClasses.add(i);
				b = true;
			}
			if (recursiveInterfaceHierarchy(i.getInterfaces(), reverseClasses,
					interfaceBound)) {
				reverseClasses.add(i);
				b = true;
			}
		}
		return b;
	}

	/**
	 * Convenience method to notify an <tt>ClassIR</tt> that an interface is
	 * parsed and parse all its methods.
	 * 
	 * @param classIr
	 *            the <tt>ClassIR</tt> to notify
	 * @param c
	 *            the interface class to parse
	 */
	protected void parseSpecificationInterface(ClassIR classIr, Class<?> c) {
		classIr.parseInterfaceClass(c);
		for (Method m : c.getDeclaredMethods()) {
			classIr.parseInterfaceMethod(m);
		}
	}

	/**
	 * Convenience method to parse a class from the hierarchy of the class that
	 * is parsed at the moment.
	 * 
	 * @param classIR
	 *            the <tt>ClassIR</tt> to notify of the parsed class and all its
	 *            declared membersById
	 * @param c
	 *            the class from the hierarchy to parse
	 */
	protected void parseHierarchyClass(ClassIR classIR, Class<?> c) {
		classIR.parseHierarchyClass(c);
		for (Constructor<?> cc : c.getConstructors()) {
			classIR.parseConstructor(cc);
		}
		for (Method m : c.getDeclaredMethods()) {
			classIR.parseMethod(m);
		}
		for (Field f : c.getDeclaredFields()) {
			classIR.parseField(f);
		}
	}

	/**
	 * Parses every declared class, method or field in <tt>clazz</tt> for
	 * <tt>DefinePreset</tt> annotations and adds them to the preset map.
	 * 
	 * @param clazz
	 *            the class that should be parsed for presets
	 */
	public void parsePresetClass(Class<?> clazz) {
		for (AnnotatedElement i : clazz.getDeclaredClasses()) {
			parsePresetElement(i);
		}
		for (AnnotatedElement i : clazz.getDeclaredConstructors()) {
			parsePresetElement(i);
		}
		for (AnnotatedElement i : clazz.getDeclaredMethods()) {
			parsePresetElement(i);
		}
		for (AnnotatedElement i : clazz.getDeclaredFields()) {
			parsePresetElement(i);
		}
	}

	/**
	 * Convenience method to add an <tt>AnnotatedElement</tt> to the preset map
	 * if it is annotated with <tt>DefinePreset</tt>
	 * 
	 * @param element
	 *            the <tt>AnnotatedElement</tt> to check and map
	 */
	protected void parsePresetElement(AnnotatedElement element) {
		DefinePreset dp = element.getAnnotation(DefinePreset.class);
		if (dp != null) {
			presets.put(dp.value(), element);
		}
	}

	/**
	 * Returns the element that has previously been mapped to
	 * <tt>annotationType</tt> by a parsed preset class.
	 * 
	 * @param annotationType
	 *            class of the annotation that may or may not be mapped to a
	 *            preset
	 * @return the <tt>AnnotatedElement</tt> that is mapped to this preset
	 *         annotation type or <tt>null</tt> if there is none
	 */
	public AnnotatedElement getPresetElement(Class<?> annotationType) {
		return presets.get(annotationType);
	}

	/**
	 * Ugly callback that is used by <tt>MemberIRs</tt> to inform the parser
	 * that there could be other matching membersById defined in the class that
	 * is parsed at the moment. Calling this will parse the membersById of the
	 * currently parsed class again.
	 */
	public void setReparseFlag() {
		reparseFlag = true;
	}
}
